/*
 * Copyright (C) 2016 Evgenii Zagumennyi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.zagum.speechrecognitionview;

import com.alibaba.fastjson.JSONObject;
import com.github.zagum.speechrecognitionview.animators.BarParamsAnimator;
import com.github.zagum.speechrecognitionview.animators.IdleAnimator;
import com.github.zagum.speechrecognitionview.animators.RmsAnimator;
import com.github.zagum.speechrecognitionview.animators.RotatingAnimator;
import com.github.zagum.speechrecognitionview.animators.TransformAnimator;
import com.github.zagum.speechrecognitionview.util.DensityUtils;
import ohos.agp.components.Attr;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.ai.asr.AsrClient;
import ohos.ai.asr.AsrIntent;
import ohos.ai.asr.AsrListener;
import ohos.ai.asr.util.AsrResultKey;
import ohos.app.Context;
import ohos.media.audio.AudioCapturer;
import ohos.media.audio.AudioCapturerInfo;
import ohos.media.audio.AudioStreamInfo;
import ohos.utils.PacMap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RecognitionProgressView extends ComponentContainer implements Component.DrawTask, ComponentContainer.ArrangeListener, AsrListener {

    //  RecognitionListener
    public static final int BARS_COUNT = 5;

    private static final int CIRCLE_RADIUS_DP = 5;
    private static final int CIRCLE_SPACING_DP = 11;
    private static final int ROTATION_RADIUS_DP = 25;
    private static final int IDLE_FLOATING_AMPLITUDE_DP = 3;

    private static final int[] DEFAULT_BARS_HEIGHT_DP = {60, 46, 70, 54, 64};

    private static final float MDPI_DENSITY = 1.5f;

    private final List<RecognitionBar> recognitionBars = new ArrayList<>();
    private Paint paint;
    private BarParamsAnimator animator;

    private int radius;
    private int spacing;
    private int rotationRadius;
    private int amplitude;

    private float density;

    private boolean isSpeaking;
    private boolean animating;
    //todo
    private AsrClient speechRecognizer;
    private AsrListener recognitionListener;

    private int barColor = -1;
    private int[] barColors;
    private int[] barMaxHeights;

    private ThreadPoolExecutor poolExecutor;
    private static final int POOL_SIZE = 3;
    private static final int ALIVE_TIME = 3;
    private static final int CAPACITY = 6;
    private AudioCapturer audioCapturer;
    private static final int SAMPLE_RATE = 16000;

    private final static String TAG = "MainThisClass->";

    private static final Map<String, Boolean> COMMAND_MAP = new HashMap<>();
    private static final int VAD_END_WAIT_MS = 2000;
    private static final int VAD_FRONT_WAIT_MS = 4800;
    private static final int TIMEOUT_DURATION = 20000;
    private boolean isRecord = false;
    private static final int BYTES_LENGTH = 1280;

    static {
        COMMAND_MAP.put("拍照", true);
        COMMAND_MAP.put("茄子", true);
    }

    public RecognitionProgressView(Context context) {
        super(context);
        init(context);
    }

    public RecognitionProgressView(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context);
    }

    public RecognitionProgressView(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /**
     * Set SpeechRecognizer which is view animated with
     *
     * @param recognizer recognizer
     */
    public void setSpeechRecognizer(AsrClient recognizer) {
        initAudioCapturer();
        if(null!=speechRecognizer){
            speechRecognizer.stopListening();
            speechRecognizer.destroy();
            speechRecognizer = null;
        }
        speechRecognizer = recognizer;
        speechRecognizer.init(setInitIntent(), this);

        // Initializes asr.
//    if (asrClient != null) {
//      asrClient.init(setInitIntent(), asrListener);
//    }
    }

    /**
     * Set RecognitionListener to receive callbacks from {@link AsrListener}
     *
     * @param listener listener
     */
    public void setRecognitionListener(AsrListener listener) {
        recognitionListener = listener;
    }

    /**
     * Starts animating view
     */
    public void play() {
        startIdleInterpolation();
        animating = true;
    }

    public void play(int type) {
        if (type == 1) {
            startRmsInterpolation();
        } else {
            startTransformInterpolation();
        }
    }

    /**
     * Stops animating view
     */
    public void stop() {
        if (animator != null) {
            animator.stop();
            animator = null;
        }
        animating = false;
        resetBars();
    }

    /**
     * Set one color to all bars in view
     *
     * @param color color
     */
    public void setSingleColor(int color) {
        barColor = color;
    }

    /**
     * Set different colors to bars in view
     *
     * @param colors - array with size = {@link #BARS_COUNT}
     */
    public void setColors(int[] colors) {
        if (colors == null) return;

        barColors = new int[BARS_COUNT];
        if (colors.length < BARS_COUNT) {
            System.arraycopy(colors, 0, barColors, 0, colors.length);
            for (int i = colors.length; i < BARS_COUNT; i++) {
                barColors[i] = colors[0];
            }
        } else {
            System.arraycopy(colors, 0, barColors, 0, BARS_COUNT);
        }
    }

    /**
     * Set sizes of bars in view
     *
     * @param heights - array with size = {@link #BARS_COUNT},
     *                if not set uses default bars heights
     */
    public void setBarMaxHeightsInDp(int[] heights) {
        if (heights == null) return;

        barMaxHeights = new int[BARS_COUNT];
        if (heights.length < BARS_COUNT) {
            System.arraycopy(heights, 0, barMaxHeights, 0, heights.length);
            for (int i = heights.length; i < BARS_COUNT; i++) {
                barMaxHeights[i] = heights[0];
            }
        } else {
            System.arraycopy(heights, 0, barMaxHeights, 0, BARS_COUNT);
        }
    }

    /**
     * Set radius of circle
     *
     * @param radius - Default value = {@link #CIRCLE_RADIUS_DP}
     */
    public void setCircleRadiusInDp(int radius) {
        this.radius = (int) (radius * density);
    }

    /**
     * Set spacing between circles
     *
     * @param spacing - Default value = {@link #CIRCLE_SPACING_DP}
     */
    public void setSpacingInDp(int spacing) {
        this.spacing = (int) (spacing * density);
    }

    /**
     * Set idle animation amplitude
     *
     * @param amplitude - Default value = {@link #IDLE_FLOATING_AMPLITUDE_DP}
     */
    public void setIdleStateAmplitudeInDp(int amplitude) {
        this.amplitude = (int) (amplitude * density);
    }

    /**
     * Set rotation animation radius
     *
     * @param radius - Default value = {@link #ROTATION_RADIUS_DP}
     */
    public void setRotationRadiusInDp(int radius) {
        this.rotationRadius = (int) (radius * density);
    }

    private void init(Context context) {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.GRAY);
        density = DensityUtils.getDeviceAttr(context).getAttributes().densityPixels;

        radius = (int) (CIRCLE_RADIUS_DP * density);
        spacing = (int) (CIRCLE_SPACING_DP * density);
        rotationRadius = (int) (ROTATION_RADIUS_DP * density);
        amplitude = (int) (IDLE_FLOATING_AMPLITUDE_DP * density);

        if (density <= MDPI_DENSITY) {
            amplitude *= 2;
        }
        addDrawTask(this);
        setArrangeListener(this::onArrange);
    }


    private void initBars() {
        final List<Integer> heights = initBarHeights();
        int firstCirclePosition = getEstimatedWidth() / 2 -
                2 * spacing -
                4 * radius;
        for (int i = 0; i < BARS_COUNT; i++) {
            int x = firstCirclePosition + (2 * radius + spacing) * i;
            RecognitionBar bar = new RecognitionBar(x, getEstimatedHeight() / 2, 2 * radius, heights.get(i), radius);
            recognitionBars.add(bar);
        }
    }

    private List<Integer> initBarHeights() {
        final List<Integer> barHeights = new ArrayList<>();
        if (barMaxHeights == null) {
            for (int i = 0; i < BARS_COUNT; i++) {
                barHeights.add((int) (DEFAULT_BARS_HEIGHT_DP[i] * density));
            }
        } else {
            for (int i = 0; i < BARS_COUNT; i++) {
                barHeights.add((int) (barMaxHeights[i] * density));
            }
        }
        return barHeights;
    }

    private void resetBars() {
        for (RecognitionBar bar : recognitionBars) {
            bar.setX(bar.getStartX());
            bar.setY(bar.getStartY());
            bar.setHeight(radius * 2);
            bar.update();
        }
    }

    private void startIdleInterpolation() {
        animator = new IdleAnimator(recognitionBars, amplitude);
        animator.start();
    }

    private void startRmsInterpolation() {
        resetBars();
        animator = new RmsAnimator(recognitionBars);
        animator.start();
    }

    private void startTransformInterpolation() {
        resetBars();
        animator = new TransformAnimator(recognitionBars, getWidth() / 2, getHeight() / 2, rotationRadius);
        animator.start();
        ((TransformAnimator) animator).setOnInterpolationFinishedListener(new TransformAnimator.OnInterpolationFinishedListener() {
            @Override
            public void onFinished() {
                startRotateInterpolation();
            }
        });
    }

    private void startRotateInterpolation() {
        animator = new RotatingAnimator(recognitionBars, getWidth() / 2, getHeight() / 2);
        animator.start();
    }


    @Override
    public void onDraw(Component component, Canvas canvas) {
        System.out.println("xxxyyy--->" + "onDraw");

        if (recognitionBars.isEmpty()) {
            System.out.println("xxxyyy--->" + "isEmpty");

            return;
        }
        System.out.println("xxxyyy--->" + "return>>>" + animating);

        if (animating) {
            animator.animate();
        }

        for (int i = 0; i < recognitionBars.size(); i++) {
            System.out.println("xxxyyy--->" + "i>>>" + i);

            RecognitionBar bar = recognitionBars.get(i);
            if (barColors != null) {
                paint.setColor(new Color(barColors[i]));
            } else if (barColor != -1) {
                paint.setColor(new Color(barColor));
            }
            canvas.drawRoundRect(bar.getRect(), radius, radius, paint);
        }
        System.out.println("xxxyyy--->" + "i>animating>>" + animating);

        if (animating) {
            getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                @Override
                public void run() {
                  invalidate();
                }
            });
        }

    }

    //12）参数l表示相对于父view的Left位置；
//          3）参数t表示相对于父view的Top位置；
//          4）参数r表示相对于父view的Right位置；
//          5）参数b表示相对于父view的Bottom位置。.
    private int left;
    private int top;
    private int right;
    private int bottom;
    private boolean isChanged = false;

    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {

        if (left == i && i1 == top && i2 == right && i3 == bottom) {
            isChanged = false;
        } else {
            isChanged = true;
        }

        left = i;
        top = i1;
        right = i2;
        bottom = i3;
        System.out.println("xxxyyy--->" + "onarrent");
        if (recognitionBars.isEmpty()) {
            initBars();
        } else if (isChanged) {
            recognitionBars.clear();
            initBars();
        }
        return true;
    }

    //  @Override
//  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//    super.onLayout(changed, left, top, right, bottom);
//    if (recognitionBars.isEmpty()) {
//      initBars();
//    } else if (changed) {
//      recognitionBars.clear();
//      initBars();
//    }
//  }
    //todo
//  @Override
//  public void onReadyForSpeech(Bundle params) {
//    if (recognitionListener != null) {
//      recognitionListener.onReadyForSpeech(params);
//    }
//  }
    @Override
    public void onInit(PacMap pacMap) {
        openAudio();
        System.out.println(TAG + "======onInit======");
        if (recognitionListener != null) {
            recognitionListener.onInit(pacMap);
        }
    }
//
//  @Override
//  public void onBeginningOfSpeech() {
//    if (recognitionListener != null) {
//      recognitionListener.onBeginningOfSpeech();
//    }
//    isSpeaking = true;
//  }

    @Override
    public void onBeginningOfSpeech() {
        System.out.println(TAG + "======onBeginningOfSpeech======");

        if (recognitionListener != null) {
            recognitionListener.onBeginningOfSpeech();
        }
        isSpeaking = true;
    }
//
//  @Override
//  public void onRmsChanged(float rmsdB) {
//    if (recognitionListener != null) {
//      recognitionListener.onRmsChanged(rmsdB);
//    }
//    if (animator == null || rmsdB < 1f) {
//      return;
//    }
//    if (!(animator instanceof RmsAnimator) && isSpeaking) {
//      startRmsInterpolation();
//    }
//    if (animator instanceof RmsAnimator) {
//      ((RmsAnimator) animator).onRmsChanged(rmsdB);
//    }
//  }

    @Override
    public void onRmsChanged(float rmsdB) {
        System.out.println(TAG + "======onRmsChanged======");

        if (recognitionListener != null) {
            recognitionListener.onRmsChanged(rmsdB);
        }
        if (animator == null || rmsdB < 1f) {
            return;
        }
        if (!(animator instanceof RmsAnimator) && isSpeaking) {
            startRmsInterpolation();
        }
        if (animator instanceof RmsAnimator) {
            ((RmsAnimator) animator).onRmsChanged(rmsdB);
        }
    }

    //
//  @Override
//  public void onBufferReceived(byte[] buffer) {
//    if (recognitionListener != null) {
//      recognitionListener.onBufferReceived(buffer);
//    }
//  }
    @Override
    public void onBufferReceived(byte[] bytes) {
        if (recognitionListener != null) {
            recognitionListener.onBufferReceived(bytes);
        }
    }

    //
//  @Override
//  public void onEndOfSpeech() {
//    if (recognitionListener != null) {
//      recognitionListener.onEndOfSpeech();
//    }
//    isSpeaking = false;
//    startTransformInterpolation();
//  }
    @Override
    public void onEndOfSpeech() {
        if (recognitionListener != null) {
            recognitionListener.onEndOfSpeech();
        }
        isSpeaking = false;
        startTransformInterpolation();
    }

    //
//  @Override
//  public void onError(int error) {
//    if (recognitionListener != null) {
//      recognitionListener.onError(error);
//    }
//  }
    @Override
    public void onError(int error) {
        System.out.println(TAG + "======onError:"+error);
//        if(error == 5 ) {
//            setSpeechRecognizer(speechRecognizer);
//        }
        if (recognitionListener != null) {
            recognitionListener.onError(error);
        }
        isSpeaking = false;
        if(error != 5 ) {
            startTransformInterpolation();
        }
    }

    //
//  @Override
//  public void onResults(Bundle results) {
//    if (recognitionListener != null) {
//      recognitionListener.onResults(results);
//    }
//  }
    @Override
    public void onResults(PacMap pacMap) {
        speechRecognizer.startListening(setStartIntent());
        if (recognitionListener != null) {
            recognitionListener.onResults(pacMap);
        }
        System.out.println(TAG + "======onResults:");

    }
//
//  @Override
//  public void onPartialResults(Bundle partialResults) {
//    if (recognitionListener != null) {
//      recognitionListener.onPartialResults(partialResults);
//    }
//  }
//

    @Override
    public void onIntermediateResults(PacMap pacMap) {
        System.out.println(TAG + "======onIntermediateResults:");
//        boolean recognizeResult = recognizeWords(result);

//    if (recognizeResult && !recognizeOver) {
//      recognizeOver = true;
////                    takePicture(new Component(getContext()));
//      speechRecognizer.stopListening();
//    }
        if (recognitionListener != null) {
            recognitionListener.onIntermediateResults(pacMap);
        }
    }

    @Override
    public void onEnd() {
        System.out.println(TAG + "======onEnd:");
        speechRecognizer.stopListening();
        speechRecognizer.startListening(setStartIntent());
    }

    //  @Override
//  public void onEvent(int eventType, Bundle params) {
//    if (recognitionListener != null) {
//      recognitionListener.onEvent(eventType, params);
//    }
//  }
    @Override
    public void onEvent(int eventType, PacMap pacMap) {
        if (recognitionListener != null) {
            recognitionListener.onEvent(eventType, pacMap);
        }
    }

    @Override
    public void onAudioStart() {

    }

    @Override
    public void onAudioEnd() {

    }


    private void initAudioCapturer() {
        System.out.println(TAG + "initAudioCapturer");

        poolExecutor =
                new ThreadPoolExecutor(
                        POOL_SIZE,
                        POOL_SIZE,
                        ALIVE_TIME,
                        TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(CAPACITY),
                        new ThreadPoolExecutor.DiscardOldestPolicy());

        AudioStreamInfo audioStreamInfo =
                new AudioStreamInfo.Builder()
                        .encodingFormat(AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT)
                        .channelMask(AudioStreamInfo.ChannelMask.CHANNEL_IN_MONO)
                        .sampleRate(SAMPLE_RATE)
                        .build();
//
        AudioCapturerInfo audioCapturerInfo = new AudioCapturerInfo.Builder().audioStreamInfo(audioStreamInfo).build();
//
        audioCapturer = new AudioCapturer(audioCapturerInfo);
    }


    private void openAudio() {
        System.out.println(TAG + "openAudio");

        if (!isRecord) {
            System.out.println(TAG + "isRecord");

            speechRecognizer.startListening(setStartIntent());
            isRecord = true;
            poolExecutor.submit(new AudioCaptureRunnable());
        }
    }

    /**
     * Obtains PCM audio streams and sends them to the ASR engine during recording.
     *
     * @since 2021-03-08
     */
    private class AudioCaptureRunnable implements Runnable {
        @Override
        public void run() {
            byte[] buffers = new byte[BYTES_LENGTH];
            audioCapturer.start();
            while (isRecord) {
                int ret = audioCapturer.read(buffers, 0, BYTES_LENGTH);
                if (ret <= 0) {
//                    HiLog.error(TAG, "======Error read data");
                } else {
                    speechRecognizer.writePcm(buffers, BYTES_LENGTH);
                }
            }
        }
    }

    private boolean recognizeWords(String result) {
        JSONObject jsonObject = JSONObject.parseObject(result);
        JSONObject resultObject = new JSONObject();
        if (jsonObject.getJSONArray("result").get(0) instanceof JSONObject) {
            resultObject = (JSONObject) jsonObject.getJSONArray("result").get(0);
        }
        String resultWord = resultObject.getString("ori_word").replace(" ", "");
        boolean command = COMMAND_MAP.getOrDefault(resultWord, false);
        System.out.println(TAG + "======" + resultWord + "===" + command);
        return command;
    }

    private AsrIntent setInitIntent() {
        AsrIntent initIntent = new AsrIntent();
        initIntent.setAudioSourceType(AsrIntent.AsrAudioSrcType.ASR_SRC_TYPE_PCM);
        initIntent.setEngineType(AsrIntent.AsrEngineType.ASR_ENGINE_TYPE_LOCAL);
        return initIntent;
    }

    private AsrIntent setStartIntent() {
        AsrIntent asrIntent = new AsrIntent();
        asrIntent.setVadEndWaitMs(VAD_END_WAIT_MS);
        asrIntent.setVadFrontWaitMs(VAD_FRONT_WAIT_MS);
        asrIntent.setTimeoutThresholdMs(TIMEOUT_DURATION);
        return asrIntent;
    }


//
//  @Override
//  protected void onDraw(Canvas canvas) {
//    super.onDraw(canvas);
//  }

}